﻿using System;
using System.Collections.Generic;
using System.Text;
using DCRF.Common.Interface;
using DCRF.Common.Definition;
using DCRF.Common.Primitive;
using DCRF.Common.Support;

namespace DCRF.Common.Connection
{
    public delegate void EndPointDelegate(string endpointKey, string connectorKey);

    /// <summary>
    /// This class encapsulates a dependency requirement for a block (Service, Interface or Property). For example
    /// if a block requires existence of a special service (provided by any block), it creates
    /// an instance of this class as one of its connectors, and mentions the name of the 
    /// service required.
    /// </summary>
    public class Connector : IGlobalConnector
    {
        #region private fields
        //used to generate random key for endpoints
        private static Random r = new Random();

        //endpoints require a method to call their block/service. 
        //for globalConnectors, this is the method.
        private IContainerBlockWeb containerWeb = null;
        private string connectorKey = null;
        private bool isGlobal = false;

        //key is a random string that only the attached of the endpoint knows. so only he is able to detach his endpoint
        private Dictionary<string, EndPoint> endpoints = new Dictionary<string, EndPoint>();
        private List<object> valueCache = new List<object>();
        private bool hasOnlyValueEndPoints = false;
        #endregion

        #region events
        public event EndPointDelegate EndPointAttached;
        public event EndPointDelegate EndPointDetaching;
        #endregion

        #region constructor

        /// <summary>
        /// only one of bcont and wcont are non-null
        /// </summary>
        /// <param name="key"></param>
        /// <param name="cont"></param>
        /// <param name="wcont"></param>
        public Connector(string key, IContainerBlockWeb wcont, bool isGlobal)
        {
            containerWeb = wcont;
            connectorKey = key;
            this.isGlobal = isGlobal;
        }

        #endregion

        #region Process

        public object ProcessRequest(params object[] args)
        {
            List<object> result = ProcessRequestAll(args);

            if (result.Count == 0) return null;

            return result[0];
        }

        public void Raise(params object[] args)
        {
            ProcessRequestAll(args);
        }

        /// <summary>
        /// This list contains result of endpoints
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public List<object> ProcessRequestAll(params object[] args)
        {
            List<object> result = null;

            if (hasOnlyValueEndPoints)
            {
                result = valueCache;
            }
            else
            {
                result = new List<object>();

                foreach (EndPoint ep in endpoints.Values)
                {
                    ep.ProcessRequest(result, args);
                }
            }

            return result;
        }

        public List<object> ProcessSingleEndPoint(string key, params object[] args)
        {
            List<object> result = new List<object>();

            endpoints[key].ProcessRequest(result, args);

            return result;
        }

        #endregion

        private void logEvent(string log)
        {
            if (containerWeb != null)
            {
                containerWeb[EventCode.LogWebEvent].Raise(LogType.Connector, log);
            }
        }

        #region Attach/Detach

        /// <summary>
        /// used to assign to endpoints
        /// </summary>
        /// <returns></returns>
        private string createRandomKey()
        {
            int len = 10;
            StringBuilder result = new StringBuilder();

            for (int i = 0; i < len; i++)
            {
                int index = r.Next(26);
                int ccase = r.Next(2);

                int charNum = (ccase == 0 ? (65+index):(97+index));
                result.Append((char)charNum);
            }

            return result.ToString();
        }

        public string ChainEndPoint(Guid blockHandle, string chainConnectorKey)
        {
            logEvent("Attaching chain-endpoint for " + connectorKey + " - " + blockHandle.ToString() + " " + chainConnectorKey);

            string key = createRandomKey();

            EndPoint ep = new EndPoint(containerWeb);
            ep.Handle = blockHandle;
            ep.ConnectorKey = chainConnectorKey;
            endpoints.Add(key, ep);
            
            hasOnlyValueEndPoints = false;

            if (EndPointAttached != null)
            {
                EndPointAttached(key, connectorKey);
            }

            return key;
        }

        public string AttachEndPoint(Guid blockHandle, string serviceName)
        {
            return AttachEndPoint(blockHandle, serviceName, null);
        }

        /// <summary>
        /// used to service connectors
        /// </summary>
        /// <param name="connectorKey"></param>
        /// <param name="blockHandle"></param>
        /// <param name="serviceName"></param>
        public string AttachEndPoint(Guid blockHandle, string serviceName, object[] predefinedValues)
        {
            logEvent("Attaching endpoint for " + connectorKey + " - " + blockHandle.ToString() + " " + serviceName);

            string key = createRandomKey();

            EndPoint ep = new EndPoint(containerWeb);
            ep.Handle = blockHandle;
            ep.ServiceName = serviceName;
            ep.PredefinedValues = predefinedValues;

            endpoints.Add(key, ep);
            hasOnlyValueEndPoints = false;

            if (EndPointAttached != null)
            {
                EndPointAttached(key, connectorKey);
            }

            return key;
        }

        public string AttachEndPoint(IConnectorSubject target, string serviceName)
        {
            logEvent("Attaching endpoint for " + connectorKey + " - " + target.GetType().ToString() + " " + serviceName);

            string key = createRandomKey();

            EndPoint ep = new EndPoint();
            ep.Target = target;
            ep.ServiceName = serviceName;

            endpoints.Add(key, ep);
            hasOnlyValueEndPoints = false;

            if (EndPointAttached != null)
            {
                EndPointAttached(key, connectorKey);
            }

            return key;
        }

        public string AttachEndPoint(object value)
        {
            logEvent("Attaching endpoint for " + connectorKey + " - " + (value == null ? "null":value.ToString()));

            string key = createRandomKey();

            EndPoint ep = new EndPoint();
            ep.Value = value;

            endpoints.Add(key, ep);
            valueCache.Add(value);

            if (endpoints.Count == valueCache.Count) hasOnlyValueEndPoints = true;

            if (EndPointAttached != null)
            {
                EndPointAttached(key, connectorKey);
            }

            return key;
        }

        public void DetachEndPoint(string key)
        {
            logEvent("Detaching endpoints of " + connectorKey + " key= "+key);

            if (EndPointDetaching != null)
            {
                EndPointDetaching(key, connectorKey);
            }

            if (endpoints[key].Value != null)
            {
                object value = endpoints[key].Value;
                valueCache.Remove(value);
            }

            endpoints.Remove(key);

            if (endpoints.Count == valueCache.Count)
            {
                hasOnlyValueEndPoints = true;
            }
            else
            {
                hasOnlyValueEndPoints = false;
            }
        }

        public void DetachAll()
        {
            logEvent("Detaching all endpoints of " + connectorKey);

            string[] keys = new string[endpoints.Keys.Count];
            endpoints.Keys.CopyTo(keys,0);

            foreach (string key in keys)
            {
                DetachEndPoint(key);
            }
        }

        #endregion

        #region Public properties
        public int EndPointsCount
        {
            get
            {
                return endpoints.Count;
            }
        }

        public ICollection<string> EndPointKeys
        {
            get
            {
                return endpoints.Keys;
            }
        }

        public object Value
        {
            get
            {
                return ProcessRequest();
            }
        }

        public List<object> ValueArray
        {
            get
            {
                return ProcessRequestAll();
            }
        }
        #endregion

        //To be used only by parent connector
        private class EndPoint
        {
            public IBlockWeb ContainerWeb = null;
            //public IContainerBlock containerBlock = null;
            /// <summary>
            /// Handle of the Block to call. If this is empty then the BlockWeb has to be used (For example when getting property values,
            /// if this is null, GlobalProperties of BlockWeb will be used).
            /// </summary>
            public Guid Handle = Guid.Empty;

            /// <summary>
            /// Service/Property/method of the Block to call. For a connector to a Block this is null
            /// The owner knows the type of each connector
            /// </summary>
            public string ServiceName = null;

            //If this is not null, lookup connectorKey in Handle (or in containerWeb if it is empty)
            public string ConnectorKey = null;

            /// <summary>
            /// sometimes we need an interface to use. In these cases Target will point to the interface and the 
            /// owner block will process the interface (method call, property operations, ...)
            /// </summary>
            public IConnectorSubject Target = null;

            /// <summary>
            /// Maybe external code, wants to assign a constant, static value to this connector, so no
            /// service is executed and no interface method is called. just this value is returned (like property)
            /// </summary>
            public object Value = null;

            //these values are used for service endpoints to pass to target service
            public object[] PredefinedValues;

            /// <summary>
            /// Connector to call
            /// </summary>
            //public Connector Connector = null;

            public EndPoint()
            {
            }

            public EndPoint(IBlockWeb wcont)
            {
                ContainerWeb = wcont;
                //containerBlock = bcont;
            }

            public override bool Equals(object obj)
            {
                if (obj == null) return false;

                EndPoint ep = obj as EndPoint;

                return (ep.Value == Value && ep.Handle == Handle && ep.ServiceName == ServiceName && ep.Target == Target);
            }

            public void ProcessRequest(List<object> result, params object[] args)
            {
                if (Target != null)
                {
                    result.Add(Target.ProcessRequest(ServiceName, args));
                }
                else if (Handle != Guid.Empty && ServiceName != null )
                {
                    //if there is no predefined value or just one missing value is present, ignore
                    if (PredefinedValues != null && PredefinedValues.Length > 0 && (PredefinedValues.Length > 1 || PredefinedValues[0] != MissingValue.Value))
                    {
                        List<object> finalValues = new List<object>(PredefinedValues);
                        int argCounter = 0;

                        for (int i = 0; i < finalValues.Count; i++)
                        {
                            if (finalValues[i] == MissingValue.Value)
                            {
                                finalValues[i] = args[argCounter++];
                            }
                        }

                        for (; argCounter < args.Length; argCounter++)
                        {
                            finalValues.Add(args[argCounter]);
                        }

                        result.Add(ContainerWeb[Handle].ProcessRequest(ServiceName, finalValues.ToArray()));
                    }
                    else
                    {
                        result.Add(ContainerWeb[Handle].ProcessRequest(ServiceName, args));
                    }
                }
                else if (ConnectorKey != null)
                {
                    if (Handle != Guid.Empty)
                    {
                        result.AddRange((List<object>)ContainerWeb[Handle].ProcessRequest("ProcessMetaService", BlockMetaServiceType.InvokeConnector, ConnectorKey, args));
                    }
                    else
                    {
                        result.AddRange(ContainerWeb[ConnectorKey].ProcessRequestAll());
                    }
                }
                else if (Value != null)
                {
                    result.Add(Value);
                }
            }

            public override string ToString()
            {
                if (Value != null)
                {
                    return Value.ToString();
                }

                if (Target != null)
                {
                    return "Interface: "+ServiceName+"@"+ Target.GetType().Name;
                }

                if (Handle != Guid.Empty && ServiceName != null)
                {
                    string result = Handle.ToString() + " : " + ServiceName;

                    if (PredefinedValues != null)
                    {
                        foreach (object item in PredefinedValues)
                        {
                            result += (item == null ? "(null)" : item.ToString());
                            result += ",";
                        }

                        if (result.EndsWith(",")) result = result.Substring(0, result.Length - 1);

                        return result;
                    }                    
                }

                if (ConnectorKey != null)
                {
                    if (Handle != Guid.Empty)
                    {
                        return "Connector: " + ConnectorKey + " @ " + Handle.ToString();
                    }
                    else
                    {
                        return "GlobalConnector: " + ConnectorKey;
                    }
                }

                return "(null)";
            }

            public override int GetHashCode()
            {
                if (Value != null)
                {
                    return Value.GetHashCode();
                }

                if (Target != null)
                {
                    return (ServiceName + Target.GetHashCode()).GetHashCode();
                }

                if (Handle != Guid.Empty)
                {
                    if (ServiceName != null)
                    {
                        return (Handle.ToString() + " : " + ServiceName).GetHashCode();
                    }
                    else
                    {
                        return (Handle.ToString() + " : " + ConnectorKey).GetHashCode();
                    }
                }

                return 0;
            }
        }

        //used for visual application
        public List<string> GetEndPointsDescription()
        {
            List<string> result = new List<string>();

            foreach (string key in endpoints.Keys)
            {
                EndPoint ep = endpoints[key];

                string item = key + "@" + ep.ToString();
                result.Add(item);
            }

            return result;
        }
        
    }

    
}
